// ------------------------------------------------------------------------
// 项目名称：Canroc.Net 
// 版权归属：Canroc（https://gitee.com/canroc）
//
// 许可证信息
// Canroc.Net项目主要遵循 Apache 许可证（版本 2.0）进行分发和使用。许可证位于源代码树根目录中的 LICENSE-APACHE 文件。
//
// 使用条款：
// 1.使用本项目应遵守相关法律法规和许可证的要求。
// 2.不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动。
// 3.任何基于本项目二次开发而产生的一切法律纠纷和责任，我们不承担任何责任
//
// 免责声明
// 对于因使用本代码而产生的任何直接、间接、偶然、特殊或后果性损害，我们不承担任何责任。
//
// 其他重要信息
// Canroc.Net 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。
// ------------------------------------------------------------------------

using Canroc.Net.Repository;
using Canroc.Net.Repository.BaseEntity;
using Furion.FriendlyException;
using SqlSugar;
using System.Reflection;

namespace Canroc.Net.Web.Entry.Component;

/// <summary>
///     ORM组件
/// </summary>
public class ORMComponent : IServiceComponent
{
    /// <summary>
    ///     格式化数据库名与表名的数据库类型
    /// </summary>
    private readonly List<DbType> _formatDatabases = [DbType.Oracle, DbType.MySql, DbType.PostgreSQL];

    /// <summary>
    ///     装载服务
    /// </summary>
    /// <param name="services">
    ///     <see cref="IServiceCollection" />
    /// </param>
    /// <param name="componentContext">组件上下文</param>
    /// <exception cref="ArgumentOutOfRangeException"></exception>
    public void Load(IServiceCollection services, ComponentContext componentContext)
    {
        var dbOption = App.GetOptions<DbConnectionOption>();
        var dbConfigs = dbOption.ConnectionConfigs.Adapt<List<ConnectionConfig>>();

        // 关闭ORM默认时间转换
        StaticConfig.AppContext_ConvertInfinityDateTime = true;
        // 设置MySql数据库的配置
        if (dbConfigs.Any(c => c.DbType == DbType.MySql))
        {
            StaticConfig.CodeFirst_MySqlCollate = "utf8mb4_0900_ai_ci";
        }

        // 读取数据库配置
        var connectionConfigs = GetDbConnectionConfigs(dbConfigs);

        // 初始化SqlSugar
        var sqlSugar = new SqlSugarScope(connectionConfigs,
            db => SqlSugarConfigureProcessing(dbOption.ConnectionConfigs, db));

        // 检查数据库
        dbConfigs.ForEach(config =>
        {
            var validateDatabase =
                dbOption.ConnectionConfigs.Find(c => c.ConfigId == config.ConfigId)?.ValidateDatabase;
            if (!validateDatabase ?? true) return;
            var dbProvider = sqlSugar.GetConnectionScope(config.ConfigId);
            ValidateDatabase(config, dbProvider);
        });

        // 单例注册
        services.AddSingleton<ISqlSugarClient>(sqlSugar);
        // 仓储注册
        services.AddScoped(typeof(SqlSugarRepository<>));
        // 事务与工作单元注册
        services.AddUnitOfWork<SqlSugarUnitOfWork>();

        "SqlSugar组件加载完成".LogInformation();
    }

    /// <summary>
    ///     获取数据库连接配置
    /// </summary>
    /// <param name="dbConfigs">数据库连接配置</param>
    /// <returns></returns>
    private List<ConnectionConfig> GetDbConnectionConfigs(List<ConnectionConfig> dbConfigs)
    {
        var connectionConfigs = new List<ConnectionConfig>();
        dbConfigs.ForEach(config =>
        {
            var connectionConfig = config.Adapt<ConnectionConfig>();
            connectionConfig.ConfigureExternalServices = new ConfigureExternalServices
            {
                // 处理列名(只处理数据库实体类)
                EntityService = (property, column) =>
                {
                    if (!typeof(IDbTable).IsAssignableFrom(property.DeclaringType)) return;

                    // 使用蛇形命名法格式化列名
                    if (_formatDatabases.Contains(config.DbType))
                        column.DbColumnName = UtilMethods.ToUnderLine(column.DbColumnName);

                    // 设置表中字段可为空
                    if (!column.IsPrimarykey &&
                        new NullabilityInfoContext().Create(property).WriteState is NullabilityState.Nullable)
                        column.IsNullable = true;

                    // 当数据库是PostgreSQL时，将DateTime类型转换为timestamp without time zone
                    if (column.PropertyInfo.PropertyType == typeof(DateTime) ||
                        column.PropertyInfo.PropertyType == typeof(DateTime?))
                    {
                        column.DataType = config.DbType switch
                        {
                            DbType.MySql => "DATETIME(3)",
                            DbType.PostgreSQL => "timestamp",
                            _ => column.DataType
                        };
                    }

                    # region 设置字段默认值

                    // 设置表中主键的默认生成函数
                    if (column.IsPrimarykey)
                    {
                        column.DefaultValue = config.DbType switch
                        {
                            DbType.SqlServer => "newid()",
                            DbType.MySql => "uuid()",
                            DbType.PostgreSQL => "gen_random_uuid()",
                            _ => column.DefaultValue
                        };
                    }

                    // 设置表中 CreateTime 列的默认生成函数
                    if (column.PropertyName == "CreateTime" && column.PropertyInfo.PropertyType == typeof(DateTime))
                    {
                        column.DefaultValue = config.DbType switch
                        {
                            DbType.SqlServer => "getdate()",
                            DbType.MySql => "now()",
                            DbType.PostgreSQL => "CURRENT_TIMESTAMP",
                            _ => column.DefaultValue
                        };
                    }

                    // 对没设置默认值的列检查
                    if (column.DefaultValue.IsNullOrEmpty() && !column.IsNullable)
                    {
                        // 如果是字符串类型，将默认值设为空字符
                        column.DefaultValue = property.PropertyType switch
                        {
                            { } t when t == typeof(string) => "''",
                            { } t when t == typeof(Guid) => Guid.Empty.ToString(),
                            { } t when t == typeof(int) || t == typeof(short) || t == typeof(long) ||
                                       t == typeof(decimal) || t == typeof(double) || t == typeof(float) => "0",
                            { } t when t == typeof(bool) => config.DbType != DbType.MySql ? "false" : "0",
                            _ => column.DefaultValue
                        };
                    }
                    else
                    {
                        // 清除可为空字段的默认值
                        if (column.IsNullable) column.DefaultValue = "";
                        // 当数据库是MySql时，将bool类型的默认值转换为0或1
                        else if (property.PropertyType == typeof(bool) && config.DbType == DbType.MySql)
                        {
                            var defaultValue = column.DefaultValue;
                            var isBool = defaultValue.Equals("false", StringComparison.CurrentCultureIgnoreCase);
                            column.DefaultValue = isBool ? "0" : "1";
                        }
                    }

                    # endregion
                },
                // 处理表名(只处理数据库实体类)
                EntityNameService = (type, entity) =>
                {
                    if (!typeof(IDbTable).IsAssignableFrom(type)) return;

                    // 使用蛇形命名法格式化表名
                    if (_formatDatabases.Contains(config.DbType))
                        entity.DbTableName = UtilMethods.ToUnderLine(entity.DbTableName);

                    // 设置禁止修改字段
                    // entity.IsDisabledUpdateAll = true;
                    entity.IsDisabledDelete = true;
                }
            };

            connectionConfigs.Add(connectionConfig);
        });

        return connectionConfigs;
    }

    /// <summary>
    ///     SqlSugar配置处理
    /// </summary>
    /// <param name="dbConfigs">数据库连接配置</param>
    /// <param name="db">数据库连接上下文</param>
    private static void SqlSugarConfigureProcessing(List<CanrocDbConfig> dbConfigs, SqlSugarClient db)
    {
        dbConfigs.ForEach(config =>
        {
            // 设置数据库配置器
            var dbProvider = db.GetConnectionScope(config.ConfigId);

            // 设置超时时间
            dbProvider.Ado.CommandTimeOut = 30;

            // 打印SQL语句
            if (config.PrintSql)
            {
                dbProvider.Aop.OnLogExecuting = (sql, pars) =>
                {
                    $"{config.DbType}:{config.ConfigId}\r\n{UtilMethods.GetNativeSql(sql, pars)}"
                        .LogInformation();
                    App.PrintToMiniProfiler("SqlSugar", "Info",
                        $"{sql}\r\n{dbProvider.Utilities.SerializeObject(pars.ToDictionary(it => it.ParameterName, it => it.Value))}");
                };
            }

            // 打印错误SQL语句
            if (config.PrintErrorSql)
            {
                dbProvider.Aop.OnError = ex =>
                {
                    if (ex.Parametres is null) return;

                    $"{config.DbType}:{config.ConfigId}\r\n{UtilMethods.GetNativeSql(ex.Sql, (SugarParameter[])ex.Parametres)}"
                        .LogError();
                    App.PrintToMiniProfiler("SqlSugar", "Error",
                        $"{ex.Message}\r\n{ex.Sql}\r\n{dbProvider.Utilities.SerializeObject(((SugarParameter[])ex.Parametres).ToDictionary(it => it.ParameterName, it => it.Value))}");
                };
            }

            // 数据审计
            dbProvider.Aop.DataExecuting = (_, entityInfo) =>
            {
                switch (entityInfo.OperationType)
                {
                    // 新增操作
                    case DataFilterType.InsertByObject:
                        // 主键Id
                        if (entityInfo.EntityColumnInfo.IsPrimarykey &&
                            entityInfo.EntityColumnInfo.PropertyInfo.PropertyType == typeof(Guid))
                        {
                            var id = entityInfo.EntityColumnInfo.PropertyInfo.GetValue(entityInfo.EntityValue);
                            if (id.IsNullOrGuidEmpty()) entityInfo.SetValue(Guid.NewGuid());
                        }
                        else
                        {
                            switch (entityInfo.PropertyName)
                            {
                                // 默认启用
                                case "IsEnable":
                                    entityInfo.SetValue(true);
                                    break;
                                // 创建时间
                                case "CreateTime":
                                    entityInfo.SetValue(DateTime.Now);
                                    break;
                                // 创建人Id
                                case "CreateUserId":
                                    {
                                        var createUserId =
                                            entityInfo.EntityColumnInfo.PropertyInfo.GetValue(entityInfo.EntityValue);
                                        if (createUserId.IsNullOrGuidEmpty())
                                        {
                                            // 尝试从App.User中获取用户Id
                                            entityInfo.SetValue(App.User?.FindFirst(ClaimConst.ClaimUserId)?.Value ??
                                                                Guid.Empty.ToString());
                                        }

                                        break;
                                    }
                                // 创建人名称
                                case "CreateUserName":
                                    {
                                        var createUserName =
                                            entityInfo.EntityColumnInfo.PropertyInfo.GetValue(entityInfo.EntityValue);
                                        if (createUserName.IsNullOrEmpty())
                                        {
                                            // 尝试从App.User中获取用户名称
                                            entityInfo.SetValue(App.User?.FindFirst(ClaimConst.ClaimName)?.Value ??
                                                                "Canroc.Net");
                                        }

                                        break;
                                    }
                            }
                        }

                        break;
                    // 更新操作
                    case DataFilterType.UpdateByObject:
                        switch (entityInfo.PropertyName)
                        {
                            // 修改时间
                            case "UpdateTime":
                                entityInfo.SetValue(DateTime.Now);
                                break;
                            // 修改人Id
                            case "UpdateUserId":
                                {
                                    var updateUserId =
                                        entityInfo.EntityColumnInfo.PropertyInfo.GetValue(entityInfo.EntityValue);
                                    if (updateUserId.IsNullOrGuidEmpty())
                                    {
                                        entityInfo.SetValue(App.User?.FindFirst(ClaimConst.ClaimUserId)?.Value ??
                                                            Guid.Empty.ToString());
                                    }

                                    break;
                                }
                            // 修改人名称
                            case "UpdateUserName":
                                {
                                    var updateUserName =
                                        entityInfo.EntityColumnInfo.PropertyInfo.GetValue(entityInfo.EntityValue);
                                    if (updateUserName.IsNullOrEmpty())
                                    {
                                        entityInfo.SetValue(App.User?.FindFirst(ClaimConst.ClaimName)?.Value ??
                                                            "Canroc.Net");
                                    }

                                    break;
                                }
                        }

                        break;
                    case DataFilterType.DeleteByObject:
                        break;
                    default:
                        return;
                }
            };

            // 配置实体假删除过滤器
            dbProvider.QueryFilter.AddTableFilter<IDeletedFilter>(u => !u.IsDelete);
        });
    }

    /// <summary>
    ///     检查数据库
    /// </summary>
    /// <param name="config">数据库配置</param>
    /// <param name="dbProvider">数据库配置器</param>
    private void ValidateDatabase(ConnectionConfig config, SqlSugarScopeProvider dbProvider)
    {
        List<string> ignoreTable = ["EntityId", "EntityBase"];

        // 初始化数据库(Oracle数据库不支持自动创建数据库)
        if (config.DbType != DbType.Oracle)
            dbProvider.DbMaintenance.CreateDatabase();

        // 从业务程序集(.Repository)中获取所有实体
        var assembly = App.Assemblies.FirstOrDefault(a => a.FullName!.Contains(".Repository")) ??
                       throw Oops.Oh("未找到实体程序集!");
        // 获取所有继承DbTable接口的实体
        var entityTypes = assembly.GetTypes()
            .Where(t => typeof(IDbTable).IsAssignableFrom(t) &&
                        t is { IsClass: true, IsAbstract: false } &&
                        !ignoreTable.Contains(t.Name))
            // 默认库时所有未指定ConfigId的实体
            .WhereIF(config.ConfigId is null, t => !t.IsDefined(typeof(TenantAttribute)))
            // 非默认库时当前ConfigId的实体
            .WhereIF(config.ConfigId is not null,
                t => t.IsDefined(typeof(TenantAttribute)) &&
                     (t.GetCustomAttributes(typeof(TenantAttribute)).First() as TenantAttribute)!
                     .configId.ToString() == config.ConfigId!.ToString())
            .OrderBy(t => t.Name)
            .ToList();
        if (entityTypes.Count <= 0) return;

        // 获取数据库中已存在的表
        var dbTables = dbProvider.DbMaintenance.GetTableInfoList(false);

        // 遍历数据库实体是否存在新建的表
        entityTypes.ForEach(entity =>
        {
            var tableName = _formatDatabases.Contains(config.DbType)
                ? UtilMethods.ToUnderLine(entity.Name)
                : entity.Name;
            var hasTable = dbTables?.Exists(dbTable => dbTable.Name == tableName) ?? false;
            if (!hasTable)
            {
                dbProvider.CodeFirst.InitTables(entity);
                $"数据库[{config.ConfigId ?? "default"}]新建表: {tableName}".LogInformation();
            }
            else
            {
                // 检查实体与数据库表字段是否一致
                var dbColumns = dbProvider.DbMaintenance.GetColumnInfosByTableName(tableName, false)
                    .Select(c => c.DbColumnName).ToList();
                var entityColumns = entity.GetProperties().ToList();
                // 查询实体中的属性与数据库中的字段差异
                entityColumns.ForEach(property =>
                {
                    var columnName = _formatDatabases.Contains(config.DbType)
                        ? UtilMethods.ToUnderLine(property.Name)
                        : property.Name;
                    if (!dbColumns.Contains(columnName))
                        $"数据库[{config.ConfigId ?? "default"}]实体[{entity.Name}]新增字段: {property.Name}".LogError();
                });
                dbColumns.ForEach(column =>
                {
                    if (!entityColumns.Exists(p =>
                            (_formatDatabases.Contains(config.DbType) ? UtilMethods.ToUnderLine(p.Name) : p.Name) ==
                            column))
                        $"数据库[{config.ConfigId ?? "default"}]表[{tableName}]删除字段: {column}".LogError();
                });
            }

            // 初始化基础数据
            InitBasicData(dbProvider, entity, assembly);
        });
    }

    /// <summary>
    ///     初始化基础数据
    /// </summary>
    /// <param name="dbProvider">数据库配置器</param>
    /// <param name="entity">数据表实体</param>
    /// <param name="assembly">种子数据的程序集</param>
    private static void InitBasicData(SqlSugarScopeProvider dbProvider, Type entity, Assembly assembly)
    {
        // 检查当前表是否为空表
        if (dbProvider.QueryableByObject(entity).Any()) return;

        // 查询基础数据是否存在
        var basicDataEntity = assembly.ExportedTypes.FirstOrDefault(t => t.Name == $"{entity.Name}BasicData");
        if (basicDataEntity?.GetMethod("GetData")?.Invoke(Activator.CreateInstance(basicDataEntity), null) is not
            IEnumerable<object> basicData) return;
        // 插入基础数据
        dbProvider.InsertableByObject(basicData.ToList()).ExecuteCommand();
    }
}